package com.training.bt.tracker;

import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.nio.ByteBuffer;
import java.security.NoSuchAlgorithmException;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;

import com.training.bt.entity.PeerActivity;
import com.training.bt.entity.User;
import com.training.bt.entity.UserPoint;
import com.training.bt.mapper.PeerActivityMapper;
import com.training.bt.mapper.UserMapper;
import com.training.bt.mapper.UserPointMapper;
import com.training.bt.service.PointsRecordService;
import com.turn.ttorrent.common.Peer;
import com.turn.ttorrent.common.Torrent;
import com.turn.ttorrent.common.protocol.TrackerMessage.AnnounceRequestMessage.RequestEvent;

public class CustomTrackedTorrent extends Torrent {
    private static class MapperHolder {
        static final UserMapper USER_MAPPER = SpringContextHolder.getBean(UserMapper.class);
        static final PeerActivityMapper PEER_ACTIVITY_MAPPER = SpringContextHolder.getBean(PeerActivityMapper.class);
        static final UserPointMapper USER_POINT_MAPPER = SpringContextHolder.getBean(UserPointMapper.class);
        static final PointsRecordService POINTS_RECORD_SERVICE = SpringContextHolder.getBean(PointsRecordService.class);
    }

    private UserMapper getUserMapper() {
        return MapperHolder.USER_MAPPER;
    }

    private PeerActivityMapper getPeerActivityMapper() {
        return MapperHolder.PEER_ACTIVITY_MAPPER;
    }

    private UserPointMapper getUserPointMapper() {
        return MapperHolder.USER_POINT_MAPPER;
    }

    private PointsRecordService getPointsRecordService() {
        return MapperHolder.POINTS_RECORD_SERVICE;
    }

    private static final Logger logger = LoggerFactory.getLogger(CustomTrackedTorrent.class);

    /** Minimum announce interval requested from peers, in seconds. */
    public static final int MIN_ANNOUNCE_INTERVAL_SECONDS = 5;

    /** Default number of peers included in a tracker response. */
    private static final int DEFAULT_ANSWER_NUM_PEERS = 30;

    /** Default announce interval requested from peers, in seconds. */
    private static final int DEFAULT_ANNOUNCE_INTERVAL_SECONDS = 10;

    private int answerPeers;
    private int announceInterval;

    /** Peers currently exchanging on this torrent. */
    private ConcurrentMap<String, CustomTrackedPeer> peers;

    /**
     * Create a new tracked torrent from meta-info binary data.
     *
     * @param torrent The meta-info byte data.
     * @throws IOException When the info dictionary can't be
     *                     encoded and hashed back to create the torrent's SHA-1
     *                     hash.
     */
    public CustomTrackedTorrent(byte[] torrent) throws IOException, NoSuchAlgorithmException {
        super(torrent, false);

        this.peers = new ConcurrentHashMap<String, CustomTrackedPeer>();
        this.answerPeers = CustomTrackedTorrent.DEFAULT_ANSWER_NUM_PEERS;
        this.announceInterval = CustomTrackedTorrent.DEFAULT_ANNOUNCE_INTERVAL_SECONDS;
    }

    public CustomTrackedTorrent(Torrent torrent) throws IOException, NoSuchAlgorithmException {
        this(torrent.getEncoded());
    }

    /**
     * Returns the map of all peers currently exchanging on this torrent.
     */
    public Map<String, CustomTrackedPeer> getPeers() {
        return this.peers;
    }

    /**
     * Add a peer exchanging on this torrent.
     *
     * @param peer The new Peer involved with this torrent.
     */
    public void addPeer(CustomTrackedPeer peer) {
        this.peers.put(peer.getHexPeerId(), peer);
    }

    /**
     * Retrieve a peer exchanging on this torrent.
     *
     * @param peerId The hexadecimal representation of the peer's ID.
     */
    public CustomTrackedPeer getPeer(String peerId) {
        return this.peers.get(peerId);
    }

    /**
     * Remove a peer from this torrent's swarm.
     *
     * @param peerId The hexadecimal representation of the peer's ID.
     */
    public CustomTrackedPeer removePeer(String peerId) {
        return this.peers.remove(peerId);
    }

    /**
     * Count the number of seeders (peers in the COMPLETED state) on this
     * torrent.
     */
    public int seeders() {
        int count = 0;
        for (CustomTrackedPeer peer : this.peers.values()) {
            if (peer.isCompleted()) {
                count++;
            }
        }
        return count;
    }

    /**
     * Count the number of leechers (non-COMPLETED peers) on this torrent.
     */
    public int leechers() {
        int count = 0;
        for (CustomTrackedPeer peer : this.peers.values()) {
            if (!peer.isCompleted()) {
                count++;
            }
        }
        return count;
    }

    /**
     * Remove unfresh peers from this torrent.
     *
     * <p>
     * Collect and remove all non-fresh peers from this torrent. This is
     * usually called by the periodic peer collector of the BitTorrent tracker.
     * </p>
     */
    public void collectUnfreshPeers() {
        for (CustomTrackedPeer peer : this.peers.values()) {
            if (!peer.isFresh()) {
                this.peers.remove(peer.getHexPeerId());
            }

            // 新增：处理超时Peer的数据库更新
            handleStalePeer(peer);
        }
    }

    private void handleStalePeer(CustomTrackedPeer peer) {
        try {
            UserMapper userMapper = getUserMapper();
            PeerActivityMapper peerActivityMapper = getPeerActivityMapper();

            User user = userMapper.findByPasskey(peer.getPasskey());
            PeerActivity activity = peerActivityMapper.selectOne(
                    new LambdaQueryWrapper<PeerActivity>()
                            .eq(PeerActivity::getUserId, user.getId())
                            .eq(PeerActivity::getTorrentHash, this.getHexInfoHash()));

            if (activity != null) {
                // 标记为停止做种
                activity.setIsSeeding(false);
                activity.setSeedingStartTime(null);
                peerActivityMapper.updateById(activity);
            }
        } catch (Exception e) {
            logger.error("处理超时Peer失败: {}", e.getMessage());
        }
    }

    /**
     * Get the announce interval for this torrent.
     */
    public int getAnnounceInterval() {
        return this.announceInterval;
    }

    /**
     * Set the announce interval for this torrent.
     *
     * @param interval New announce interval, in seconds.
     */
    public void setAnnounceInterval(int interval) {
        if (interval <= 0) {
            throw new IllegalArgumentException("Invalid announce interval");
        }

        this.announceInterval = interval;
    }

    /**
     * Update this torrent's swarm from an announce event.
     *
     * <p>
     * This will automatically create a new peer on a 'started' announce event,
     * and remove the peer on a 'stopped' announce event.
     * </p>
     *
     * @param event      The reported event. If <em>null</em>, means a regular
     *                   interval announce event, as defined in the BitTorrent
     *                   specification.
     * @param peerId     The byte-encoded peer ID.
     * @param hexPeerId  The hexadecimal representation of the peer's ID.
     * @param ip         The peer's IP address.
     * @param port       The peer's inbound port.
     * @param uploaded   The peer's reported uploaded byte count.
     * @param downloaded The peer's reported downloaded byte count.
     * @param left       The peer's reported left to download byte count.
     * @return The peer that sent us the announce request.
     */
    public CustomTrackedPeer update(RequestEvent event, ByteBuffer peerId,
            String hexPeerId, String ip, int port, long uploaded, long downloaded,
            long left) throws UnsupportedEncodingException {
        CustomTrackedPeer peer;
        CustomTrackedPeer.PeerState state = CustomTrackedPeer.PeerState.UNKNOWN;

        UserMapper userMapper = getUserMapper();
        PeerActivityMapper peerActivityMapper = getPeerActivityMapper();
        UserPointMapper userPointMapper = getUserPointMapper();
        PointsRecordService pointsRecordService = getPointsRecordService();

        if (RequestEvent.STARTED.equals(event)) {
            String passkey = this.extractPasskeyFromAnnounce();
            logger.info("STARTED event for torrent: {}, passkey: {}",
                    this.getHexInfoHash(), passkey);

            peer = new CustomTrackedPeer(this, ip, port, peerId, passkey);
            this.addPeer(peer);

            User user = userMapper.findByPasskey(passkey);
            if (user == null) {
                logger.error("USER NOT FOUND for passkey: {}", passkey);
                throw new IllegalStateException("User not found");
            }
            logger.info("Found user: {}", user.getId());

            // 检查是否已存在记录
            PeerActivity existing = peerActivityMapper.selectOne(
                    new LambdaQueryWrapper<PeerActivity>()
                            .eq(PeerActivity::getUserId, user.getId())
                            .eq(PeerActivity::getTorrentHash, this.getHexInfoHash()));
            logger.info("Existing PeerActivity: {}", existing != null ? "found" : "not found");

            boolean isSeeding = (left == 0);

            // 创建或更新记录
            PeerActivity peerActivity = PeerActivity.builder()
                    .userId(user.getId())
                    .torrentHash(this.getHexInfoHash())
                    .uploaded(uploaded)
                    .downloaded(downloaded)
                    .lastAnnounceTime(LocalDateTime.now())
                    .seedingStartTime(isSeeding ? LocalDateTime.now() : null)
                    .isSeeding(isSeeding)
                    .build();

            try {
                if (existing == null) {
                    peerActivityMapper.insert(peerActivity);
                    logger.info("INSERTED new PeerActivity");
                } else {
                    peerActivity.setId(existing.getId());
                    peerActivityMapper.updateById(peerActivity);
                    logger.info("UPDATED existing PeerActivity");
                }
            } catch (Exception e) {
                logger.error("DATABASE ERROR: {}", e.getMessage(), e);
            }
        } else if (RequestEvent.STOPPED.equals(event)) {
            peer = this.removePeer(hexPeerId);
            if (peer == null) {
                // 处理未正常注册但需要退出的情况
                peer = new CustomTrackedPeer(this, ip, port, peerId, extractPasskeyFromAnnounce());
                peer.update(CustomTrackedPeer.PeerState.STOPPED, uploaded, downloaded, left);
            }
            state = CustomTrackedPeer.PeerState.STOPPED;

            User user = userMapper.findByPasskey(peer.getPasskey());
            PeerActivity peerActivity = peerActivityMapper.selectOne(new LambdaQueryWrapper<PeerActivity>()
                    .eq(PeerActivity::getUserId, user.getId())
                    .eq(PeerActivity::getTorrentHash, this.getHexInfoHash()));

            if (peerActivity != null) {
                if (peerActivity.getIsSeeding() && peerActivity.getSeedingStartTime() != null) {
                    LocalDateTime now = LocalDateTime.now();
                    Duration duration = Duration.between(peerActivity.getSeedingStartTime(), now);
                    long seconds = duration.getSeconds();
                    int pointsEarned = (int) (seconds / 1800); // 每30分钟=1800秒1分

                    // 安全更新用户积分
                    UserPoint userPoint = userPointMapper.findByUserId(user.getId());
                    if (userPoint != null) {
                        userPoint.setPoint(userPoint.getPoint() + pointsEarned);
                        userPointMapper.updateById(userPoint);

                        // 记录积分变更
                        pointsRecordService.pointsAdd(user.getId(), pointsEarned);
                    } else {
                        logger.warn("UserPoint not found for user: {}", user.getId());
                    }
                }
            }
            // 更新做种状态
            peerActivity.setIsSeeding(false);
            peerActivity.setSeedingStartTime(null);
            peerActivityMapper.updateById(peerActivity);
        } else if (RequestEvent.COMPLETED.equals(event)) {
            peer = this.getPeer(hexPeerId);
            state = CustomTrackedPeer.PeerState.COMPLETED;

            User user = userMapper.findByPasskey(peer.getPasskey());
            PeerActivity peerActivity = peerActivityMapper.selectOne(new LambdaQueryWrapper<PeerActivity>()
                    .eq(PeerActivity::getUserId, user.getId())
                    .eq(PeerActivity::getTorrentHash, this.getHexInfoHash()));
            if (peerActivity != null) {
                if (peer.isCompleted() && peerActivity.getSeedingStartTime() == null) {
                    peerActivity.setIsSeeding(true);
                    peerActivity.setSeedingStartTime(LocalDateTime.now());
                }
                peerActivityMapper.updateById(peerActivity);
            }

        } else if (RequestEvent.NONE.equals(event)) {
            peer = this.getPeer(hexPeerId);
            state = CustomTrackedPeer.PeerState.STARTED;
            User user = userMapper.findByPasskey(peer.getPasskey());
            PeerActivity peerActivity = peerActivityMapper.selectOne(new LambdaQueryWrapper<PeerActivity>()
                    .eq(PeerActivity::getUserId, user.getId())
                    .eq(PeerActivity::getTorrentHash, this.getHexInfoHash()));
            if (peerActivity != null) {
                // 如果用户已完成下载且正在做种，但未记录开始时间，则初始化
                if (peer.isCompleted() && peerActivity.getIsSeeding() && peerActivity.getSeedingStartTime() == null) {
                    peerActivity.setSeedingStartTime(LocalDateTime.now());
                    peerActivityMapper.updateById(peerActivity);
                }
                // 更新最后一次报告时间（可选）
                peerActivity.setLastAnnounceTime(LocalDateTime.now());
                peerActivityMapper.updateById(peerActivity);
            }
        } else {
            throw new IllegalArgumentException("Unexpected announce event type!");
        }

        peer.update(state, uploaded, downloaded, left);
        return peer;
    }

    /**
     * Get a list of peers we can return in an announce response for this
     * torrent.
     *
     * @param peer The peer making the request, so we can exclude it from the
     *             list of returned peers.
     * @return A list of peers we can include in an announce response.
     */
    public List<Peer> getSomePeers(CustomTrackedPeer peer) {
        List<Peer> peers = new LinkedList<Peer>();

        // Extract answerPeers random peers
        List<CustomTrackedPeer> candidates = new LinkedList<CustomTrackedPeer>(this.peers.values());
        Collections.shuffle(candidates);

        int count = 0;
        for (CustomTrackedPeer candidate : candidates) {
            // Collect unfresh peers, and obviously don't serve them as well.
            if (!candidate.isFresh() ||
                    (candidate.looksLike(peer) && !candidate.equals(peer))) {
                logger.debug("Collecting stale peer {}...", candidate);
                this.peers.remove(candidate.getHexPeerId());
                continue;
            }

            // Don't include the requesting peer in the answer.
            if (peer.looksLike(candidate)) {
                continue;
            }

            // Collect unfresh peers, and obviously don't serve them as well.
            if (!candidate.isFresh()) {
                logger.debug("Collecting stale peer {}...",
                        candidate.getHexPeerId());
                this.peers.remove(candidate.getHexPeerId());
                continue;
            }

            // Only serve at most ANSWER_NUM_PEERS peers
            if (count++ > this.answerPeers) {
                break;
            }

            peers.add(candidate);
        }

        return peers;
    }

    /**
     * Load a tracked torrent from the given torrent file.
     *
     * @param torrent The abstract {@link File} object representing the
     *                <tt>.torrent</tt> file to load.
     * @throws IOException When the torrent file cannot be read.
     */
    public static CustomTrackedTorrent load(File torrent) throws IOException, NoSuchAlgorithmException {
        byte[] data = FileUtils.readFileToByteArray(torrent);
        return new CustomTrackedTorrent(data);
    }

    private String extractPasskeyFromAnnounce() {
        // 1) 从父类 Torrent 拿到 announce-list（List<List<URI>>）
        List<List<URI>> announceList = this.getAnnounceList();
        if (announceList == null || announceList.isEmpty() || announceList.get(0).isEmpty()) {
            // 没有配置任何 announce URI
            return null;
        }

        // 2) 取第一层列表的第一个 URI（通常是主 tracker URI）
        URI announceUri = announceList.get(0).get(0);
        if (announceUri == null) {
            return null;
        }

        // 3) 把 URI 转成字符串，提取 query
        String announceUrl = announceUri.toString();
        try {
            String query = announceUri.getQuery(); // 例如 "passkey=ABCDE12345&info_hash=..."
            if (query == null) {
                return null;
            }
            // 4) 拆分 query 参数，找以 "passkey=" 开头的项
            for (String param : query.split("&")) {
                if (param.startsWith("passkey=")) {
                    return param.substring("passkey=".length());
                }
            }
        } catch (Exception e) {
            // URISyntaxException 不会再这里抛，因为 URI 已经合法
            logger.warn("解析 announce URI 时遇到问题: {}", announceUrl, e);
        }
        return null;
    }
}